/**
 * Copyright (C) 2021 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.szd.messagebubbleview;

import ohos.agp.animation.Animator;
import ohos.agp.animation.Animator.StateChangedListener;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.agp.utils.TextAlignment;
import ohos.agp.window.service.Display;
import ohos.agp.window.service.DisplayAttributes;
import ohos.agp.window.service.DisplayManager;
import ohos.app.Context;
import ohos.global.resource.NotExistException;
import ohos.global.resource.Resource;
import ohos.global.resource.ResourceManager;
import ohos.global.resource.WrongTypeException;
import ohos.media.image.ImageSource;
import ohos.media.image.PixelMap;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.Optional;

/**
 * RoundImageView
 *
 * @author AnBetter
 * @since 2021-03-31
 */
public class MessageBubbleView extends Component implements
        Component.TouchEventListener,
        Component.LayoutRefreshedListener,Component.DrawTask {
    /**
     * 原位
     */
    public static final int STATE_NORMAL = 0;
    /**
     * 消失
     */
    public static final int STATE_DISAPPEAR = 1;
    /**
     * 拖拽
     */
    public static final int STATE_DRAGING = 2;
    /**
     * 移动（无粘连效果）
     */
    public static final int STATE_MOVE = 3;

    private static final float NUM_ZEROPOINTFIVE = 0.5f;
    private static final int NUMSIXTEEN = 16;
    private static final int NUMTWO = 2;
    private static final int NUMFOUR = 4;

    private static final int NUMFIVEHUNDREDEIGHTYEIGHT = 588;
    private static final int NUMONETHOUSAND = 1164;

    private static final int NUMTEN = 10;
    private static final int NUMSEVEN = 7;
    private static final double NUMFOURNINE = 4.99;
    private static final int NUMFIVEHUNDRED = 500;
    private static final int NUMTWOHUNDRED = 200;
    private static final float NUMZEROPOINTFIVESEVEN = 0.571429f;
    private static final int NUMMINUSFOUR = -4;
    private static final float ZERO = 0.0f;
    private static final float ZEROONE = 0.1f;
    Paint mPaint;
    Paint textPaint;
    Paint disappearPaint;
    Path mPath;
    float textMove; // 字体垂直偏移量
    float centerRadius; // 中心园半径
    float dragRadius = dp2px(getContext(),NUMSIXTEEN); // 拖拽圆半径
    int dragCircleX;
    int centerCircleX;
    int dragCircleY;
    int centerCircleY;
    float dis; // 两个圆的距离
    String mNumber;
    int maxDragLength; // 最大可拖拽距离
    float textSize; // 用户设定的字体大小
    Color textColor; // 用户设定的字体颜色
    Color circleColor; // 用户设定的圆圈颜色
    int[] disappearPic;
    PixelMap[] disappearBitmap;
    RectFloat bitmapRect;
    int bitmapIndex; // 消失动画播放图片的index
    boolean isStartDisappear; // 判断是否正在播放消失动画，防止死循环重复绘制
    ActionListener actionListener;
    float tempX;
    float tempY;
    float tempEndX;
    float tempEndY;
    /**
     * 当前状态
     */
    int curState;

    private Component mRoot;

    private TouchListener mTouchListener;

    private BoomListener mBoomListener;
    private boolean isNeedInit;

    /** MessageBubbleView
     * @param context
     * @param attrSet
     * @throws NotExistException
     * @throws WrongTypeException
     * @throws IOException
     */
    public MessageBubbleView(Context context, AttrSet attrSet)
            throws NotExistException, WrongTypeException, IOException {
        super(context, attrSet);
        circleColor = attrSet.getAttr("circleColor").get().getColorValue();
        textColor = attrSet.getAttr("textColor").get().getColorValue();
        textSize = attrSet.getAttr("textSize").get().getDimensionValue();
        textSize = dp2px(context,NUMSIXTEEN);
        centerRadius = attrSet.getAttr("radius").get().getDimensionValue();
        centerRadius = dp2px(context,NUMSIXTEEN);
        mNumber = attrSet.getAttr("textNumber").get().getStringValue();
        if (mNumber == null) {
            mNumber = "";
        }
        init();
        setTouchEventListener(this::onTouchEvent);
        setLayoutRefreshedListener(this::onRefreshed);
    }

    public void setBoomListener(BoomListener boomListener) {
        this.mBoomListener = boomListener;
    }

    public void setTouchListener(TouchListener touchListener) {
        this.mTouchListener = touchListener;
    }

    /** setRoot
     * @param root
     */
    public void setRoot(Component root) {
        mRoot = root;
        this.addDrawTask(this::onDraw);
    }

    /** init
     * @throws NotExistException
     * @throws WrongTypeException
     * @throws IOException
     */
    public void init() throws NotExistException, WrongTypeException, IOException {
        this.setVisibility(Component.VISIBLE);
        initPaint();
        isStartDisappear = false;
        Paint.FontMetrics textFontMetrics = textPaint.getFontMetrics();
        textMove = NumCalcUtil.subtract(-textFontMetrics.ascent,
                NumCalcUtil.divide(NumCalcUtil.add(-textFontMetrics.ascent,
                        textFontMetrics.descent),NUMTWO));
        mPath = new Path();
        if (centerRadius <= NUMTWO) { // 如果不是第一次创建，上次的拖动删除会因为中心圆半径随着拖放变为零
            centerRadius = dragRadius;
        } else {
            dragRadius = centerRadius;
        }
        maxDragLength = (int) (NUMFOUR * dragRadius);
        isNeedInit = true;

        dragCircleX = centerCircleX;
        dragCircleY = centerCircleY;
        if (disappearPic == null) {
            disappearPic = new int[]{ResourceTable.Media_explosion_one,ResourceTable.Media_explosion_two,
                ResourceTable.Media_explosion_three,
                ResourceTable.Media_explosion_four,
                ResourceTable.Media_explosion_five};
        }
        disappearBitmap = new PixelMap[disappearPic.length];
        for (int mi = 0; mi < disappearPic.length; mi++) {
            ResourceManager manager = getResourceManager();
            String mediaPath = getResourceManager().getMediaPath(disappearPic[mi]);
            Resource resource1 = manager.getRawFileEntry(mediaPath).openRawFile();
            ImageSource.SourceOptions sourceOptions = new ImageSource.SourceOptions();
            ImageSource imageSource = ImageSource.create(resource1,sourceOptions);
            ImageSource.DecodingOptions decodingOptions = new ImageSource.DecodingOptions();
            disappearBitmap[mi] = imageSource.createPixelmap(decodingOptions);
        }
        curState = STATE_NORMAL;
        this.setClipEnabled(false);
    }

    private void initPaint() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(circleColor);
        textPaint = new Paint();
        textPaint.setAntiAlias(true);
        textPaint.setColor(textColor);
        textPaint.setTextAlign(TextAlignment.CENTER);
        textPaint.setTextSize((int) textSize);
        disappearPaint = new Paint();
        disappearPaint.setAntiAlias(true);
        disappearPaint.setFilterBitmap(false);
    }

    private void pointDown(MmiPoint point) {
        if (mTouchListener != null) {
            mTouchListener.touchDown();
        }
        tempX = point.getX();
        tempY = point.getY();
        if (curState != STATE_DISAPPEAR) {
            dis = new BigDecimal(Math.hypot(new BigDecimal(centerCircleX).
                      subtract(new BigDecimal(point.getX())).doubleValue(),
                      new BigDecimal(centerCircleY).subtract(new BigDecimal(point.getY())).
                      doubleValue())).floatValue();
            if (dis < NumCalcUtil.add(centerRadius,NUMTEN)) {
                curState = STATE_DRAGING;
            } else {
                curState = STATE_NORMAL;
            }
        }
    }

    private void pointUp(MmiPoint point) {
        if (mTouchListener != null) {
            mTouchListener.touchUp();
        }
        if (curState == STATE_DRAGING || curState == STATE_MOVE) {
            dis = new BigDecimal(Math.hypot(new BigDecimal(tempX).subtract(new BigDecimal(dragCircleX)).doubleValue(),
                  new BigDecimal(tempY).subtract(new BigDecimal(dragCircleY)).doubleValue())).floatValue();
            if (dis > maxDragLength) {
                curState = STATE_DISAPPEAR;
                isStartDisappear = true;
                disappearAnim();
            } else {
                tempEndX = point.getX();
                tempEndY = point.getY();
                restoreAnim();
            }
            invalidate();
        }
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        MmiPoint point = touchEvent.getPointerPosition(touchEvent.getIndex());
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                pointDown(point);
                break;
            case TouchEvent.POINT_MOVE:
                dragCircleX = (int) point.getX();
                dragCircleY = (int) point.getY();
                if (curState == STATE_DRAGING) {
                    dis = new BigDecimal(Math.hypot(new BigDecimal(tempX).
                          subtract(new BigDecimal(dragCircleX)).doubleValue(),
                          new BigDecimal(tempY).subtract(new BigDecimal(dragCircleY)).
                          doubleValue())).floatValue();
                    if (dis <= maxDragLength - maxDragLength / NUMSEVEN) {
                        centerRadius = new BigDecimal(dragRadius).subtract(new BigDecimal(dis).
                        divide(new BigDecimal(NUMFOUR))).floatValue();
                        if (actionListener != null) {
                            actionListener.onDrag();
                        }
                    } else {
                        centerRadius = 0;
                        curState = STATE_MOVE;
                    }
                } else if (curState == STATE_MOVE && actionListener != null) { // 超出最大拖拽距离，则中间的圆消失
                    actionListener.onMove();
                }
                invalidate();
                break;
            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP:
                pointUp(point);
                break;
            default:
                break;
        }
        return true;
    }

    /**
     * 气泡消失动画
     */
    private void disappearAnim() {
        bitmapRect = new RectFloat(dragCircleX - (int) dragRadius,
                dragCircleY - (int) dragRadius,dragCircleX + (int) dragRadius, dragCircleY + (int) dragRadius);
        AnimatorValue disappearAnimator = new AnimatorValue();
        disappearAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                bitmapIndex = (int)(v * NUMFOURNINE);
                invalidate();
            }
        });

        disappearAnimator.setStateChangedListener(new StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }

            @Override
            public void onStop(Animator animator) {
            }

            @Override
            public void onCancel(Animator animator) {
            }

            @Override
            public void onEnd(Animator animator) {
                isStartDisappear = false;
                if (actionListener != null) {
                    actionListener.onDisappear();
                }

                if (mBoomListener != null) {
                    mBoomListener.boom();
                }
            }

            @Override
            public void onPause(Animator animator) {
            }

            @Override
            public void onResume(Animator animator) {
            }
        });
        disappearAnimator.setCurveType(Animator.CurveType.LINEAR);
        disappearAnimator.setDuration(NUMFIVEHUNDRED);
        disappearAnimator.start();
    }

    @Override
    public void invalidate() {
        super.invalidate();

        if (this.mRoot != null) {
            this.mRoot.invalidate();
        }
    }

    /**
     * 气泡复原动画
     */
    private void restoreAnim() {
        AnimatorValue valueAnimator = new AnimatorValue();
        valueAnimator.setDuration(NUMTWOHUNDRED);
        valueAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float v) {
                float mRest = NUMZEROPOINTFIVESEVEN;
                float temp = new BigDecimal(Math.pow(new BigDecimal(NUMTWO).doubleValue(),
                    new BigDecimal(NUMMINUSFOUR).multiply(new BigDecimal(v)).doubleValue())).floatValue();
                double sin1 = new BigDecimal(v).subtract(new BigDecimal(mRest).
                        divide(new BigDecimal(NUMFOUR))).doubleValue();
                double sin2 = new BigDecimal(NUMTWO).multiply(new BigDecimal(Math.PI)).doubleValue();
                double sin3 = new BigDecimal(sin1).multiply(new BigDecimal(sin2)).doubleValue();
                float fraction = -(float)(temp * Math.sin(sin3 / mRest) + 1);
                dragCircleX = (int) NumCalcUtil.add(tempEndX,NumCalcUtil.
                        multiply(fraction,NumCalcUtil.subtract(tempEndX,centerCircleX)));
                dragCircleY = (int) NumCalcUtil.add(tempEndY,NumCalcUtil.
                        multiply(fraction,NumCalcUtil.subtract(tempEndY,centerCircleY)));
                invalidate();
            }
        });

        valueAnimator.setStateChangedListener(new StateChangedListener() {
            @Override
            public void onStart(Animator animator) {
            }
            @Override
            public void onStop(Animator animator) {
            }
            @Override
            public void onCancel(Animator animator) {
            }
            @Override
            public void onEnd(Animator animator) {
                centerRadius = dragRadius;
                curState = STATE_NORMAL;
                if (actionListener != null) {
                    actionListener.onRestore();
                }
            }
            @Override
            public void onPause(Animator animator) {
            }
            @Override
            public void onResume(Animator animator) {
            }
        });
        valueAnimator.start();
    }

    /**
     * 绘制贝塞尔曲线
     *
     * @param canvas canvas
     */
    private void drawBezier(Canvas canvas) {
        dis = (float) Math.hypot(centerCircleX - dragCircleX, centerCircleY - dragCircleY);
        if (dis == ZERO) {
            dis = ZEROONE;
        }
        float sin = (centerCircleY - dragCircleY) / dis;
        float cos = (centerCircleX - dragCircleX) / dis;
        float dragCircleStartX = NumCalcUtil.subtract(new BigDecimal(dragCircleX).
                floatValue(),NumCalcUtil.multiply(dragRadius,sin));
        float dragCircleStartY = NumCalcUtil.add(new BigDecimal(dragCircleY).
                floatValue(),NumCalcUtil.multiply(dragRadius,cos));
        float centerCircleEndX = NumCalcUtil.subtract(new BigDecimal(centerCircleX).
                floatValue(),NumCalcUtil.multiply(centerRadius,sin));
        float centerCircleEndY = NumCalcUtil.add(new BigDecimal(centerCircleY).
                floatValue(),NumCalcUtil.multiply(centerRadius,cos));
        float centerCircleStartX = NumCalcUtil.add(new BigDecimal(centerCircleX).
                floatValue(),NumCalcUtil.multiply(centerRadius,sin));
        float centerCircleStartY = NumCalcUtil.subtract(new BigDecimal(centerCircleY).
                floatValue(),NumCalcUtil.multiply(centerRadius,cos));
        float dragCircleEndX = NumCalcUtil.add(new BigDecimal(dragCircleX).
                floatValue(),NumCalcUtil.multiply(dragRadius,sin));
        float dragCircleEndY = NumCalcUtil.subtract(new BigDecimal(dragCircleY).
                floatValue(),NumCalcUtil.multiply(dragRadius,cos));
        float controlX = (float) (centerCircleX + dragCircleX) / NUMTWO;
        float controlY = (float) (dragCircleY + centerCircleY) / NUMTWO;
        mPath.reset();
        mPath.moveTo(centerCircleStartX, centerCircleStartY);
        mPath.quadTo(controlX, controlY, dragCircleEndX, dragCircleEndY);
        mPath.lineTo(dragCircleStartX, dragCircleStartY);
        mPath.quadTo(controlX, controlY, centerCircleEndX, centerCircleEndY);
        mPath.close();

        canvas.drawPath(mPath, mPaint);
    }

    /**
     * 重置
     *
     * @throws NotExistException
     * @throws WrongTypeException
     * @throws IOException
     */
    public void resetBezierView() throws NotExistException, WrongTypeException, IOException {
        init();
        invalidate();
    }

    /**
     * 设置显示的消息数量(超过99需要自己定义为"99+")
     *
     * @param number 消息的数量
     */
    public void setNumber(String number) {
        mNumber = number;
        invalidate();
    }

    @Override
    public void onRefreshed(Component component) {
        invalidate();
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (isNeedInit) {
            centerCircleX = getWidth() / NUMTWO;
            centerCircleY = getHeight() / NUMTWO;
            isNeedInit = false;
        }
        if (mNumber.isEmpty()) {
            return;
        }
        if (curState == STATE_NORMAL) {
            canvas.drawCircle(centerCircleX, centerCircleY, centerRadius, mPaint);
            canvas.drawText(textPaint,mNumber, centerCircleX, NumCalcUtil.add(centerCircleY,textMove));
        }
        if (curState == STATE_DRAGING) {
            canvas.drawCircle(centerCircleX, centerCircleY, centerRadius, mPaint);
            canvas.drawCircle(dragCircleX, dragCircleY, dragRadius, mPaint);
            drawBezier(canvas);
            canvas.drawText(textPaint,mNumber, dragCircleX, NumCalcUtil.add(dragCircleY,textMove));
        }
        if (curState == STATE_MOVE) {
            canvas.drawCircle(dragCircleX, dragCircleY, dragRadius, mPaint);
            canvas.drawText(textPaint,mNumber, dragCircleX, NumCalcUtil.add(dragCircleY,textMove));
        }
        if (curState == STATE_DISAPPEAR && isStartDisappear) {
            if (disappearBitmap != null) {
                PixelMapHolder pic = new PixelMapHolder(disappearBitmap[bitmapIndex]);
                canvas.drawPixelMapHolderRect(pic,bitmapRect,disappearPaint);
            }
        }
    }

    /**
     * ActionListener
     *
     * @author AnBetter
     * @since 2021-03-31
     */
    public interface ActionListener {
        /**
         * 被拖动时
         */
        void onDrag();

        /**
         * 消失后
         */
        void onDisappear();

        /**
         * 拖动距离不足，气泡回到原位后
         */
        void onRestore();

        /**
         * 拖动时超出了最大粘连距离，气泡单独移动时
         */
        void onMove();
    }

    public void setOnActionListener(ActionListener actionListener1) {
        this.actionListener = actionListener1;
    }

    /** getScreenPiex
     *
     * @param context
     * @return DisplayAttributes
     */
    public static DisplayAttributes getScreenPiex(Context context) {
        // 获取屏幕密度
        Optional<Display> display = DisplayManager.getInstance().getDefaultDisplay(context);
        DisplayAttributes displayAttributes = null;
        if (display.isPresent()) {
            displayAttributes = display.get().getAttributes();
        }
        return displayAttributes;
    }

    /** dp2px
     *
     * @param context
     * @param dp
     * @return displayAttributes
     */
    public static float dp2px(Context context, float dp) {
        DisplayAttributes displayAttributes = getScreenPiex(context);
        float numTemp = NumCalcUtil.multiply(dp,displayAttributes.scalDensity);
        return NumCalcUtil.add(numTemp,NUM_ZEROPOINTFIVE);
    }

    /**
     * BoomListener
     *
     * @since 2021-03-31
     */
    public interface BoomListener {
        /**
         * boom
         */
        void boom();
    }

    /**
     * TouchListener
     *
     * @since 2021-03-31
     */
    public interface TouchListener {
        /**
         * touchDown
         */
        void touchDown();

        /**
         * touchUp
         */
        void touchUp();
    }
}
